package com.example.testproj;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.SoundPool;
import android.os.Handler;
import android.util.Log;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import com.example.testproj.utils.FileUtils;
import com.example.testproj.utils.SinWave;

import java.util.Random;

/**
 * @des:
 * @author: 芮勤
 * @date: 2019/2/22 17:04
 * @see {@link }
 */
public class CpuTest {
    private final Handler handler;
    private final ImageView imageView;
    private Context context;
    private static final String TAG = "CpuTest";
    public SoundPool soundPool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);

    // Chip 8 有35个opcodes（cpu指令），其中每个都是双字节长(2 bytes)
    short opcode;
    //· Chip 8共有4K内存
    /**
     * 0x000-0x1FF - Chip 8解释器(包含用于显示的字体)
     * 0x050-0x0A0 - 用于生成 4x5 像素的字体集合 (从’0’到’F’)
     * 0x200-0xFFF - 游戏ROM 与工作RAM
     */
    byte[] memory = new byte[4096];
    // CPU 寄存器：Chip 8 有16个单字节(1 byte)寄存器，名字为V0,V1...到VF. 前15个寄存器为通用寄存器，最后一个寄存器(VF)是个进位标志(carry flag)
    byte[] v = new byte[16];
    //· 索引寄存器I（Index register，暂译为“索引寄存器”）
    int index;
    // 程序计数器PC（program counter）
    int pc;
    //· Chip 8的显示是二值化的，总共有2048个像素 (64 x 32)，每个像素有两种状态1或0（常见0表示黑，1表示白）
    boolean[] gfx = new boolean[64 * 32];
    int[] displayArray = new int[64 * 32];

    //· Chip 8没有中断以及硬件寄存器（hardware register，暂译为“硬件寄存器”），不过有两个timer（计时器），
    // 当它们被设定为一个正值时候，他们应当以opcode的执行频率倒计时直至0为止。（即每执行一条opcode后，
    // 如果当前两个timer为正，应当对其进行--操作。opcode的理想情况是被运行在60hz，这是需要实现者去想办法保证的）
    int delay_timer;
    int soud_timer;
    //栈在这里被用于在调用子函数之前保存当前的pc(程序计数器)的位置
    int stack[] = new int[16];
    //需要一个栈顶指针SP(stack pointer)去指向当前的栈顶
    int sp;
    //Chip 8的输入是一个16个按键的键盘(0x0-0xF)
    short[] key = new short[16];
    short[] chip8_fontset = {
            0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
            0x20, 0x60, 0x20, 0x20, 0x70, // 1
            0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
            0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
            0x90, 0x90, 0xF0, 0x10, 0x10, // 4
            0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
            0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
            0xF0, 0x10, 0x20, 0x40, 0x40, // 7
            0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
            0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
            0xF0, 0x90, 0xF0, 0x90, 0x90, // A
            0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
            0xF0, 0x80, 0x80, 0x80, 0xF0, // C
            0xE0, 0x90, 0x90, 0x90, 0xE0, // D
            0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
            0xF0, 0x80, 0xF0, 0x80, 0x80  // F
    };
    boolean isDrawPlay = true;

    private long times = 0;

    private long endTime = 0;

    private int speed = 6;

    int Hz = 0;
    int waveLen = 0;
    int length = 0;
    byte[] wave;
    AudioTrack audioTrack;

    boolean isPlaySound = false;
    private Thread soudsThread;


    public CpuTest(Context context, Handler handler, ImageView imageView) {
        this.context = context;
        this.handler = handler;
        this.imageView = imageView;

        initSound();
    }

    public void run(String name) throws Exception {
        initialize();
        loadGame(name);

        for (; ; ) {
            endTime = System.currentTimeMillis() + (17 / speed);
            emulateCycle();
            timerCycle();
            if (isDrawPlay) {
                isDrawPlay = false;
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        // Log.d(TAG, "run: GUESS");
                        Bitmap bmp = Bitmap.createBitmap(64, 32
                                , Bitmap.Config.RGB_565);

                        String content="";
                        for (int i = 0; i < gfx.length; i++) {
                            if (gfx[i]) {
                                displayArray[i] = Color.rgb(0, 0, 0);
                                content+=i+".";
                            } else {

                                displayArray[i] = Color.rgb(0xff, 0xff, 0xff);
                                content+="*";
                            }
                            if (i%64==0){
                               Log.d("qin",content);
                               content="";
                            }
                        }
                        bmp.setPixels(displayArray, 0, 64, 0, 0, 64, 32);//将处理后的像素信息赋给新图
//                        Bitmap bmp= BitmapFactory.decodeResource(context.getResources(),R.mipmap.ic_launcher);
                        imageView.setImageBitmap(bmp);
                    }
                });

            }
            long sleepTime = endTime - System.currentTimeMillis();
            if (sleepTime > 0) {
                Thread.sleep(sleepTime);
            }

//            try {

//            Thread.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }

//            if (times == 140) {
//                Log.d(TAG, "run: times==" + times);
//            }
//
//            Log.d(TAG, "run: times==" + times);
//            times++;
        }
    }

    private void initSound() {
        start(200);
        soudsThread = new Thread() {
            @Override
            public void run() {
                super.run();
                boolean isPlaying = false;
                while (true) {
                    if (isPlaySound) {
                        if (!isPlaying) {
                            audioTrack.play();
                        }
                        isPlaying = true;
                        audioTrack.write(wave, 0, 200);
                    } else if (isPlaying) {
                        isPlaying = false;
                        audioTrack.stop();
                    }

                }
            }
        };
        soudsThread.start();
    }

    private void timerCycle() throws Exception {
        if (delay_timer > 0) {
            delay_timer--;
        }

        if (soud_timer > 0) {
            soud_timer--;
            isPlaySound = true;
        } else {
            isPlaySound = false;
        }
//        if (delay_timer<0){
//            throw new Exception();
//        }
    }

    private void emulateCycle() throws Exception {
        int vx = 0;
        int tmpValue1 = 0;
        int tmpValue2 = 0;

        Log.d(TAG, "emulateCycle: pc===" + pc);

//        printByte(memory[pc]);
//        printByte(memory[pc + 1]);

//        printShort((short) (memory[pc] << 8));
//        printShort((short) (memory[pc+1]));
//        printShort(((short)0));
        opcode = (short) (memory[pc] << 8 | (0x00ff & memory[pc + 1]));

//        Log.d(TAG, "emulateCycle: index" + index);
//        Log.d(TAG, "emulateCycle: memory[745]" + memory[745]);
        int com1 = memory[pc] >> 4 & 0xf;
        int com2 = memory[pc] & 0x0f;
        int com3 = memory[pc + 1] >> 4 & 0xf;
        int com4 = memory[pc + 1] & 0x0f;
        Log.d(TAG, "emulateCycle: com===" + com1 + "," + com2 + "," + com3 + "," + com4);

        if (opcode == -4089) {
            // throw new Exception();
        }


        printShort((short) (opcode));
//        printShort((short) (opcode & 0xf000));
        switch (opcode & 0xf000) {
            case 0x0000:
                //Call
                switch (opcode) {
                    case 0xe0:
                        disp_clear();
                        isDrawPlay = true;
                        pc = (short) (pc + 2);
                        break;
                    case 0xee:
                        pull();
                        break;
                    default:
                        pc = (short) (pc + 2);
                }
                break;
            case 0x1000:
                //Flow
                pc = (short) (opcode & 0xfff);
                break;
            case 0x2000:
                //Flow
                push();
                pc = (short) (opcode & 0xfff);
                break;
            case 0x3000:
                if (v[com2] == (opcode & 0xff)) {
                    pc += 2;
                }
                pc += 2;
                //Cond
                break;
            case 0x4000:
                if (v[com2] != (opcode & 0xff)) {
                    pc += 2;
                }
                pc += 2;
                break;
            case 0x5000:
                if (v[com2] == v[com3]) {
                    pc += 2;
                }
                pc += 2;
                break;
            case 0x6000:
                vx = opcode & 0xff;

                v[com2] = (byte) vx;
                pc += 2;
                break;
            case 0x7000:


                vx = byte2Int(v[com2]) + opcode & 0xff;
//                if (vx > 0xff) {
//                    v[0xf] = 1;
//                } else {
//                    v[0xf] = 0;
//                }
//                printInt(vx);
//                printByte((byte) vx);
                v[com2] = (byte) vx;
                pc += 2;
                break;
            case 0x8000:
                switch (com4) {
                    case 0:
                        v[com2] = v[com3];
                        break;
                    case 1:
                        v[com2] = (byte) (v[com2] | v[com3]);
                        break;
                    case 2:
                        v[com2] = (byte) (v[com2] & v[com3]);
                        break;
                    case 3:
                        v[com2] = (byte) (v[com2] ^ v[com3]);
                        break;
                    case 4:

//
//                        tmpValue1 = v[com3];
//                        if (tmpValue1 < 0) {
//                            tmpValue1 = tmpValue1 + 0xff;
//                        }
//                        tmpValue2 = v[com2];
//                        if (tmpValue2 < 0) {
//                            tmpValue2 = tmpValue2 + 0xff;
//                        }
//                        vx = tmpValue1 + tmpValue2;
//
//                        if (vx > 0xff) {
//                            v[0xf] = 1;
//                            vx = vx - 0xff;
//                        } else {
//                            v[0xf] = 0;
//                        }
//
//                        v[com2] = (byte) vx;
                        if (byte2Int(v[com2]) > (0xFF - byte2Int(v[com3]))) //即VY > 255 - VX
                            v[0xF] = 1; //出现了溢出，则把VF置为1
                        else
                            v[0xF] = 0;
                        v[com2] = (byte) (byte2Int(v[com2]) + byte2Int(v[com3]));//VX += VY
                        break;
                    case 5:

//                        tmpValue1 = v[com3];
//                        if (tmpValue1 < 0) {
//                            tmpValue1 = tmpValue1 + 0xff;
//                        }
//                        tmpValue2 = v[com2];
//                        if (tmpValue2 < 0) {
//                            tmpValue2 = tmpValue2 + 0xff;
//                        }
//
//                        vx = tmpValue2 - tmpValue1;
//                        if (vx < 0) {
//                            Log.d(TAG, "");
//                            vx = vx + 0xff;
//                        }
//                        v[com2] = (byte) vx;

                        if (byte2Int(v[com3]) > byte2Int(v[com2]))//即VX > VY
                            v[0xF] = 0; //有借位发生，置为0
                        else
                            v[0xF] = 1;
                        v[com2] = (byte) (byte2Int(v[com2]) - byte2Int(v[com3]));
                        break;
                    case 6:
                        v[0xF] = (byte) (v[com2] & 0x1);//取出最低位，给VF
                        v[com2] = (byte) (v[com2] >> 1);
                        break;
                    case 7:

                        if (byte2Int(v[com2]) > byte2Int(v[com3]))//即VX > VY
                            v[0xF] = 0; //有借位发生，置为0
                        else
                            v[0xF] = 1;
                        v[com2] = (byte) (byte2Int(v[com3]) - byte2Int(v[com2]));
//                        tmpValue1 = v[com3];
//                        if (tmpValue1 < 0) {
//                            tmpValue1 = tmpValue1 + 0xff;
//                        }
//                        tmpValue2 = v[com2];
//                        if (tmpValue2 < 0) {
//                            tmpValue2 = tmpValue2 + 0xff;
//                        }
//
//                        vx = tmpValue1 - tmpValue2;
//                        if (vx < 0) {
//                            Log.d(TAG, "");
//                            vx = vx + 0xff;
//                        }
//                        v[com2] = (byte) vx;
                        break;
                    case 0xe:
                        v[0xF] = (byte) (v[com2]>>7 & 0x01);//取出最高位，给VF
                        v[com2] = (byte) (v[com2] << 1);
                        break;
                }
                pc = (short) (pc + 2);
                break;
            case 0x9000:
                if (v[com2] != v[com3]) {
                    pc += 2;
                }
                pc = (short) (pc + 2);
                break;
            case 0xa000:
                index = (short) (opcode & 0xfff);
                pc = (short) (pc + 2);
                break;
            case 0xb000:
                pc = (short) (v[0] + (opcode & 0xfff));
                break;
            case 0xc000:
                short rand = (short) new Random().nextInt(0xff);
                v[com2] = (byte) (rand & (opcode & 0xff));
                //  Log.d(TAG, "使用了随机数");
                pc = (short) (pc + 2);
                break;
            case 0xd000:
                //   Log.d(TAG, "开始绘画");
                draw(v[com2], v[com3], com4);
                isDrawPlay = true;
                pc = (short) (pc + 2);
                break;
            case 0xe000:
                switch (opcode & 0xff) {
                    case 0x9E:
                        if (key[v[(opcode & 0x0F00) >> 8]] != 0) {
                            pc += 2;
                        }
                        break;
                    case 0xA1:
                        if (key[v[(opcode & 0x0F00) >> 8]] == 0) {
                            pc += 2;
                        }
                        break;
                }
                pc = (short) (pc + 2);
                break;
            case 0xf000:
                switch (opcode & 0xff) {
                    case 0x07:
                        v[com2] = (byte) delay_timer;
                        break;
                    case 0x0a:

                        boolean keyPress = false;

                        for (int i = 0; i < 16; ++i) {
                            if (key[i] != 0) {
                                v[com2] = (byte) i;
                                keyPress = true;//这里只存了最后一个按下的按键到VX
                            }
                        }

                        //如果没有按键按下，则返回
                        if (!keyPress) {
                            return;
                        }


                        break;
                    case 0x15:
                        printByte(v[com2]);
                        delay_timer = v[com2];
                        break;
                    case 0x18:
                        soud_timer = v[com2];
                        break;
                    case 0x1e:

//                        int valueIndex=index;
//                        if (valueIndex<0){
//                            valueIndex=valueIndex+0xffff;
//                        }
//                        tmpValue2=v[com2];
//                        if (tmpValue2<0){
//                            tmpValue2=tmpValue2+0xff;
//                        }
//                        index = (short) (valueIndex+ tmpValue2);
//                        if ((valueIndex+tmpValue2)>0xffff){
//                            v[0xf]=1;
//                            index= (short) (index-0xffff);
//                        }else {
//                            v[0xf]=0;
//                        }

                        if (index + byte2Int(v[com2]) > 0xFFF) {
                            v[0xF] = 1;
                            index = index + byte2Int(v[com2]) - 0xFFF;
                        } else {
                            v[0xF] = 0;
                            index = index + byte2Int(v[com2]);
                        }


                        break;
                    case 0x29:
//                        Log.d(TAG, "0x29 com2:== " + com2);
//                        Log.d(TAG, "我要画:" + v[com2]);
                        printShort(v[com2]);
                        index = (short) (v[com2] * 5);
                        break;
                    case 0x33:
                        int value = v[com2];
//                        Log.d(TAG, "emulateCycle: v[com2]=="+v[com2]);
                        if (value < 0) {
                            value = value + 256;
                        }
                        memory[index] = (byte) (value / 100);//取得十进制百位
                        memory[index + 1] = (byte) ((value / 10) % 10);//取得十进制十位
                        memory[index + 2] = (byte) ((value % 100) % 10);//取得十进制个位

//                        Log.d(TAG, "emulateCycle:  memory[index + 1]"+ memory[index + 1]);
                        break;
                    case 0x55:
                        for (int i = 0; i <= com2; i++) {
                            memory[index + i] = v[i];
                        }
                        //    index += com2 + 1;
                        //Log.d(TAG, "run: times================================================="+times);
                        break;
                    case 0x65:

                        for (int i = 0; i <= com2; i++) {
                            v[i] = memory[index + i];
                        }
                        //   index += com2 + 1;
                        break;
                }
                pc = (short) (pc + 2);
                break;
        }

    }

    private void printByte(byte b) {
        String str = "";
        for (int i = 0; i < 8; i++) {
            str = (b >> i & 0x01) + str;

        }
        //  Log.d(TAG, "printByte:" + str);
    }

    private void printShort(short b) {
        String str = "";
        for (int i = 0; i < 16; i++) {
            str = (b >> i & 0x01) + str;

        }
        //    Log.d(TAG, "printByte:" + str);
    }

    private void printInt(int b) {
        String str = "";
        for (int i = 0; i < 32; i++) {
            str = (b >> i & 0x01) + str;

        }
        //    Log.d(TAG, "printByte:" + str);
    }

    private void draw(byte px, byte py, int height) {
        v[0xf] = 0;
        for (int y = 0; y < height; y++) {
            byte bm = memory[index + y];
            for (int x = 0; x < 8; x++) {
//                int number=px + x + ((py + y) * 64);
//                if (((bm << x) & 0x80) == 0x80){//依次检查新值中每一位是否为1
//                    if (gfx[number]) {
//                        v[0xF] = 1;//设置VF为1
//                        gfx[number] =true;
//                    }else {
//                        gfx[number] =false;
//                    }
//
//                   //gfx中用1个byte来表示1个像素，其值为0或1。这个异或相当于取反
//                }
                int nowX = byte2Int(px) + x;
                int nowY = byte2Int(py) + y;

                // 显示溢出处理
                if (nowX > 64 - 1) {
                    while (nowX > 64 - 1) nowX -= 64;
                }
                if (nowX < 0) {
                    while (nowX < 0) nowX += 64;
                }

                if (nowY > 32 - 1) {
                    while (nowY > 32 - 1) nowY -= 32;
                }
                if (nowY < 0) {
                    while (nowY < 0) nowY += 32;
                }

                int number = (nowX + nowY * 64);
                if (((bm << x) & 0x80) == 0) {
                    // gfx[number] = false;
                } else {
                    if (gfx[number]) {
                        v[0xf] = 1;
                        gfx[number] = false;
                    } else {
                        gfx[number] = true;
                    }

                }


            }
        }

    }

    private void disp_clear() {
        for (int i = 0; i < gfx.length; i++) {
            gfx[i] = false;
        }
        isDrawPlay = true;
    }

    //入栈
    private void push() {
        stack[sp] = pc;
        sp++;
    }

    //出栈
    private void pull() {
        sp--;
        pc = stack[sp];
        pc += 2;
    }

    /**
     * 加载游戏rom
     *
     * @param name
     */
    private void loadGame(String name) {
        byte[] rom = FileUtils.getFromAssets(context, "roms/" + name);

        if (rom != null) {
            Log.d(TAG, "loadGame: rom==" + rom.length);
            for (int i = 0; i < rom.length; i++) {
                memory[0x200 + i] = rom[i];
            }
        }
//        for (int i = 0; i < rom.length; i+=2) {
//            memory[0x200 + i] = rom[i];
//
//            opcode = (short) (memory[i] << 8 | (0x00ff & memory[i + 1]));
//
//            Log.d(TAG, "emulateCycle: pc" + (i+0x200));
//            int com1 = memory[i+0x200] >> 4 & 0xf;
//            int com2 = memory[i+0x200] & 0x0f;
//            int com3 = memory[i +0x200+ 1] >> 4 & 0xf;
//            int com4 = memory[i +0x200+ 1] & 0x0f;
//            Log.d(TAG, "emulateCycle: com===" + com1 + "," + com2 + "," + com3 + "," + com4);
//        }
    }

    private void initialize() {
        pc = 0x200;
        opcode = 0;
        index = 0;
        sp = 0;
        for (int i = 0; i < chip8_fontset.length; i++) {
            memory[i] = (byte) chip8_fontset[i];
        }
        disp_clear();


//        for (int i=0;i<gfx.length;i++){
//            gfx[i]= Color.rgb(0x00,0xff,0xff);
//        }
    }

    private int byte2Int(byte b) {
        int value = 0;
        for (int i = 0; i < 8; i++) {
            if ((b >> i & 0x01) != 0) {
                value += Math.pow(2, i);
            }

        }
        return value;
    }

    /**
     * 设置频率
     *
     * @param rate
     */
    public void start(int rate) {


        if (rate > 0) {
            Hz = rate;
            waveLen = 44100 / Hz;
            length = waveLen * Hz;
            wave = new byte[length];
            audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                    AudioFormat.CHANNEL_CONFIGURATION_STEREO, // CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_8BIT, length, AudioTrack.MODE_STREAM);
            //生成正弦波
            wave = SinWave.sin(wave, waveLen, length);
            if (audioTrack != null) {
                audioTrack.play();
            }
        } else {
            return;
        }
    }

    public void pressKey(int pos) {
        key[pos] = 1;
    }

    public void unPressKey(int pos) {
        key[pos] = 0;
    }

    public void onDestroy() {
        soudsThread.interrupt();
        memory = null;
    }
}
